﻿using System;
using System.Collections.Generic;
using System.Text;

namespace BioCompEx2
{
    class Solution
    {
        private int[] sol;
        private int grade;
        private Random rand;
        private String decodedText;

        public int Grade
        {
            get
            {
                return grade;
            }
        }

        public String DecodedText
        {
            get
            {
                return decodedText;
            }
        }

        //Default CTOR - initializes a random array of mappings without duplications
        //and then calculates the decoded text string and the grade
        public Solution()
        {
            sol = new int[26];
            rand = SolutionSet.rand;
            int randInt;
            int[] chosenEncoding = new int[26];
            for (int i = 0; i < chosenEncoding.Length; chosenEncoding[i] = 0, i++) ;
            for (int i = 0; i < sol.Length; i++)
            {
                do
                {
                    randInt = rand.Next(97, 123);
                } while (chosenEncoding[randInt-97] == 1);  //avoid duplications
                chosenEncoding[randInt-97] = 1;
                sol[i] = randInt;
            }
            calculateMyGrade();
        }

        //CTOR with a prepared mapping array
        //only calculates the decoded text string and the grade
        public Solution(int[] solution)
        {
            rand = SolutionSet.rand;
            sol = solution;
            calculateMyGrade();
        }

        //buildes the decoded text according to the mapping and calculated the grade
        private void calculateMyGrade()
        {
            decodedText = "";
            grade = 0;
            char[] encodedText = DataForm.encodedText.ToCharArray();
            for (int i = 0; i < encodedText.Length; i++)
            {
                if (!(encodedText[i] >= 'a' && encodedText[i] <= 'z'))
                    decodedText += " ";
                else
                {
                    //get the ASCII of the letter to decode
                    int letter = Convert.ToInt32(encodedText[i]);
                    //get the mapping for the letter and add the decoded letter to the decoded text
                    decodedText += char.ConvertFromUtf32(sol[letter - 97]);
                }
            }
            char[] separator = new char[1];
            separator[0] = ' ';
            String[] splittedText = decodedText.Split(separator);
            for (int i = 0; i < splittedText.Length; i++)
            {
                if (splittedText[i] != "")
                {
                    //if a word exists completely we add its lebgth squared to the grade
                    if (DataForm.wordDict.ContainsKey(splittedText[i]))
                        grade += (int)Math.Pow(splittedText[i].Length,2);
                    else
                    {
                        /***************************************************************/
                        /***was initially used with the word keys to calculate grades***/
                        /***************************************************************/
                        /*long curKey = KeyedWord.calcWordKey(splittedText[i]);
                        long distance = curKey;
                        String curWord = null;
                        LinkedList<KeyedWord> keyedWords = (LinkedList<KeyedWord>)(DataForm.keyedDict[splittedText[i].Length]);
                        foreach (KeyedWord word in keyedWords)
                        {
                            curWord = word.Word;
                            if ((word.Key - curKey) < distance)
                            {
                                distance = word.Key - curKey;
                                dictWord = curWord;
                                break;
                            }
                        }
                        if (dictWord == null)
                            dictWord = curWord;
                        char[] curLetters = splittedText[i].ToCharArray();
                        char[] dictLetters = dictWord.ToCharArray();
                        for (int j = 0; j < curLetters.Length; j++)
                        {
                            if (curLetters[j] == dictLetters[j])
                                grade++;
                        }*/
                        /**************************************************************/
                        //else we get the list of all words with same length and calculate the best match
                        int maxGrade = 0;
                        LinkedList<KeyedWord> keyedWords = (LinkedList<KeyedWord>)(DataForm.keyedDict[splittedText[i].Length]);
                        foreach (KeyedWord word in keyedWords)
                        {
                            int curGrade = getCompareGrade(splittedText[i], word.Word);
                            if (curGrade > maxGrade)
                                maxGrade = curGrade;
                        }
                        grade += maxGrade;
                    }
                }
            }
        }

        //compare 2 strring letter by letter and count how many exact matches exist
        private int getCompareGrade(String curWord, String dictWord)
        {
            int curGrade = 0;
            char[] curLetters = curWord.ToCharArray();
            char[] dictLetters = dictWord.ToCharArray();
            for (int j = 0; j < curLetters.Length; j++)
            {
                if (curLetters[j] == dictLetters[j])
                    curGrade++;
            }
            return curGrade;
        }

        //return the right part of the mapping array starting from index to the end
        //index is enclusive
        public int[] getValsFromIndex(int index)
        {
            int[] vals = new int[sol.Length - index];
            for (int i = 0; i < vals.Length; i++)
            {
                vals[i] = sol[i + index];                
            }
            return vals;
        }

        //return the left part of the mapping array starting from 0 to index
        //index is exclusive
        public int[] getValsToIndex(int index)
        {
            int[] vals = new int[index];
            for (int i = 0; i < vals.Length; i++)
            {
                vals[i] = sol[i];
            }
            return vals;
        }

        //crosses 2 solutions to create AB+CD=>AD+CB
        //the split is done in a random location
        public Solution[] crossover(Solution solution)
        {
            Solution[] sols = new Solution[2];
            int index = rand.Next(0, 26);
            int[] newSol = new int[26];
            int[] mappedLetters = new int[26];
            for (int i = 0; i < mappedLetters.Length; mappedLetters[i] = 0, ++i) ;
            //"left" son
            //get the left side from the father
            for (int i = 0; i < index; i++)
            {
                newSol[i] = sol[i];
                ++mappedLetters[sol[i]-97];
            }
            //get the right side from the mother
            int[] vals = solution.getValsFromIndex(index);
            for (int i = index; i < newSol.Length; i++)
            {
                newSol[i] = vals[i - index];
                ++mappedLetters[vals[i - index] - 97];
            }
            //fix duplications
            newSol = fixDoubleMapping(newSol, mappedLetters);
            sols[0] = new Solution(newSol);
            //"right" son
            //get the left side from the mother
            newSol = new int[26];
            mappedLetters = new int[26];
            for (int i = 0; i < mappedLetters.Length; mappedLetters[i] = 0, ++i) ;
            vals = solution.getValsToIndex(index);
            for (int i = 0; i < index; i++)
            {
                newSol[i] = vals[i];
                ++mappedLetters[vals[i] - 97];
            }
            //get the right side from the father
            for (int i = index; i < newSol.Length; i++)
            {
                newSol[i] = sol[i - index];
                ++mappedLetters[sol[i - index] - 97];
            }
            //fix duplications
            newSol = fixDoubleMapping(newSol, mappedLetters);
            sols[1] = new Solution(newSol);
            //return the 2 new sons
            return sols;
        }


        //matches an unmapped letter to a double mapped one
        private int[] fixDoubleMapping(int[] newSol, int[] mappedLetters)
        {
            String zeroLetters = "";
            String doubleLetters = "";
            for (int i = 0; i < mappedLetters.Length; i++)
            {
                if (mappedLetters[i] == 0)
                    zeroLetters += char.ConvertFromUtf32(i + 97);
                else if (mappedLetters[i] > 1)
                    doubleLetters += char.ConvertFromUtf32(i + 97);
            }
            /* fixs double mappings - for each double mapping there's a zero letter.
             * there's at worst case maximum 2 mappings for the same letter
             */
            int length = doubleLetters.Length;
            char[] d = doubleLetters.ToCharArray();
            char[] z = zeroLetters.ToCharArray();
            for (int i = 0; i < length; i++)
            {
                Boolean done = false;
                String dLetter = Convert.ToString(d[i]);
                String zLetter = Convert.ToString(z[i]);
                for (int j = 0; !done && j < newSol.Length; j++)
                {
                    if (char.ConvertFromUtf32(newSol[j]) == dLetter)
                    {
                        done = true;
                        newSol[j] = Convert.ToInt32(Convert.ToChar(zLetter));
                    }
                }
            }
            return newSol;
        }

        //randomly select a letter, randomly mutate it upwards or downwards and switch the 2 letters
        public void mutate()
        {
            int index = rand.Next(0, 26);
            int upOrDown = rand.Next(0, 1);
            int newCode;
            if (upOrDown == 1)  //up
                newCode = sol[index] + 1;
            else
                newCode = sol[index] - 1;
            Boolean done = false;
            for (int i = 0; !done && i < sol.Length; i++)
            {
                if (sol[i] == newCode)
                {
                    sol[i] = sol[index];
                    sol[index] = newCode;
                    done = true;
                }
            }
        }
    }
}
